Panduan komprehensif algoritma penelusuran pohon: Depth-First Search (DFS) dan Breadth-First Search (BFS). Pelajari prinsip, implementasi, dan performanya.
Algoritma Penelusuran Pohon: Depth-First Search (DFS) vs. Breadth-First Search (BFS)
Dalam ilmu komputer, penelusuran pohon (juga dikenal sebagai pencarian pohon atau penjelajahan pohon) adalah proses mengunjungi (memeriksa dan/atau memperbarui) setiap node dalam struktur data pohon, tepat satu kali. Pohon adalah struktur data fundamental yang banyak digunakan dalam berbagai aplikasi, mulai dari merepresentasikan data hierarkis (seperti sistem file atau struktur organisasi) hingga memfasilitasi algoritma pencarian dan pengurutan yang efisien. Memahami cara menelusuri pohon sangat penting untuk dapat bekerja secara efektif dengannya.
Dua pendekatan utama untuk penelusuran pohon adalah Depth-First Search (DFS) dan Breadth-First Search (BFS). Setiap algoritma menawarkan keunggulan yang berbeda dan cocok untuk berbagai jenis masalah. Panduan komprehensif ini akan membahas DFS dan BFS secara rinci, mencakup prinsip, implementasi, kasus penggunaan, dan karakteristik performanya.
Memahami Struktur Data Pohon
Sebelum mendalami algoritma penelusuran, mari kita tinjau secara singkat dasar-dasar struktur data pohon.
Apa Itu Pohon?
Pohon adalah struktur data hierarkis yang terdiri dari node-node yang terhubung oleh edge. Pohon memiliki node akar (node paling atas), dan setiap node dapat memiliki nol atau lebih node anak. Node tanpa anak disebut node daun. Karakteristik utama sebuah pohon meliputi:
- Akar: Node paling atas dalam pohon.
- Node: Elemen dalam pohon, berisi data dan berpotensi referensi ke node anak.
- Edge: Koneksi antara dua node.
- Induk: Node yang memiliki satu atau lebih node anak.
- Anak: Node yang terhubung langsung ke node lain (induknya) dalam pohon.
- Daun: Node tanpa anak.
- Subpohon: Pohon yang dibentuk oleh sebuah node dan semua keturunannya.
- Kedalaman sebuah node: Jumlah edge dari akar ke node.
- Tinggi sebuah pohon: Kedalaman maksimum dari setiap node dalam pohon.
Jenis-Jenis Pohon
Beberapa variasi pohon ada, masing-masing dengan properti dan kasus penggunaan spesifik. Beberapa jenis umum meliputi:
- Pohon Biner: Pohon di mana setiap node memiliki paling banyak dua anak, yang biasanya disebut anak kiri dan anak kanan.
- Pohon Pencarian Biner (BST): Pohon biner di mana nilai setiap node lebih besar dari atau sama dengan nilai semua node di subpohon kirinya dan kurang dari atau sama dengan nilai semua node di subpohon kanannya. Properti ini memungkinkan pencarian yang efisien.
- Pohon AVL: Pohon pencarian biner yang menyeimbangkan diri sendiri yang mempertahankan struktur yang seimbang untuk memastikan kompleksitas waktu logaritmik untuk operasi pencarian, penyisipan, dan penghapusan.
- Pohon Merah-Hitam: Pohon pencarian biner penyeimbang diri lainnya yang menggunakan properti warna untuk menjaga keseimbangan.
- Pohon N-ary (atau Pohon K-ary): Pohon di mana setiap node dapat memiliki paling banyak N anak.
Depth-First Search (DFS)
Depth-First Search (DFS) adalah algoritma penelusuran pohon yang menjelajahi sejauh mungkin di sepanjang setiap cabang sebelum melakukan penelusuran balik (backtracking). Algoritma ini memprioritaskan untuk masuk jauh ke dalam pohon sebelum menjelajahi simpul saudara (siblings). DFS dapat diimplementasikan secara rekursif atau iteratif menggunakan stack.
Algoritma DFS
Ada tiga jenis penelusuran DFS yang umum:
- Penelusuran Inorder (Kiri-Akar-Kanan): Mengunjungi subpohon kiri, lalu node akar, dan terakhir subpohon kanan. Ini umumnya digunakan untuk pohon pencarian biner karena ia mengunjungi node dalam urutan terurut.
- Penelusuran Preorder (Akar-Kiri-Kanan): Mengunjungi node akar, lalu subpohon kiri, dan terakhir subpohon kanan. Ini sering digunakan untuk membuat salinan pohon.
- Penelusuran Postorder (Kiri-Kanan-Akar): Mengunjungi subpohon kiri, lalu subpohon kanan, dan terakhir node akar. Ini umumnya digunakan untuk menghapus pohon.
Contoh Implementasi (Python)
Berikut adalah contoh Python yang mendemonstrasikan setiap jenis penelusuran DFS:
class Node:
def __init__(self, data):
self.data = data
self.left = None
self.right = None
# Penelusuran Inorder (Kiri-Akar-Kanan)
def inorder_traversal(root):
if root:
inorder_traversal(root.left)
print(root.data, end=" ")
inorder_traversal(root.right)
# Penelusuran Preorder (Akar-Kiri-Kanan)
def preorder_traversal(root):
if root:
print(root.data, end=" ")
preorder_traversal(root.left)
preorder_traversal(root.right)
# Penelusuran Postorder (Kiri-Kanan-Akar)
def postorder_traversal(root):
if root:
postorder_traversal(root.left)
postorder_traversal(root.right)
print(root.data, end=" ")
# Contoh Penggunaan
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
print("Penelusuran Inorder:")
inorder_traversal(root) # Output: 4 2 5 1 3
print("\\nPenelusuran Preorder:")
preorder_traversal(root) # Output: 1 2 4 5 3
print("\\nPenelusuran Postorder:")
postorder_traversal(root) # Output: 4 5 2 3 1
DFS Iteratif (dengan Stack)
DFS juga dapat diimplementasikan secara iteratif menggunakan stack. Berikut adalah contoh penelusuran preorder iteratif:
def iterative_preorder(root):
if root is None:
return
stack = [root]
while stack:
node = stack.pop()
print(node.data, end=" ")
# Masukkan anak kanan terlebih dahulu agar anak kiri diproses lebih dulu
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
#Contoh Penggunaan (pohon yang sama seperti sebelumnya)
print("\\nPenelusuran Preorder Iteratif:")
iterative_preorder(root)
Kasus Penggunaan DFS
- Menemukan jalur antara dua node: DFS dapat secara efisien menemukan jalur dalam graf atau pohon. Pertimbangkan perutean paket data melintasi jaringan (direpresentasikan sebagai graf). DFS dapat menemukan rute antara dua server, bahkan jika ada beberapa rute.
- Pengurutan topologis: DFS digunakan dalam pengurutan topologis graf asiklik berarah (DAG). Bayangkan menjadwalkan tugas di mana beberapa tugas bergantung pada tugas lainnya. Pengurutan topologis mengatur tugas-tugas dalam urutan yang menghormati ketergantungan ini.
- Mendeteksi siklus dalam graf: DFS dapat mendeteksi siklus dalam graf. Deteksi siklus penting dalam alokasi sumber daya. Jika proses A menunggu proses B dan proses B menunggu proses A, hal itu dapat menyebabkan kebuntuan (deadlock).
- Memecahkan labirin: DFS dapat digunakan untuk menemukan jalur melalui labirin.
- Parsing dan evaluasi ekspresi: Compiler menggunakan pendekatan berbasis DFS untuk parsing dan evaluasi ekspresi matematika.
Keunggulan dan Kekurangan DFS
Keunggulan:
- Mudah diimplementasikan: Implementasi rekursif seringkali sangat ringkas dan mudah dipahami.
- Efisiensi memori untuk pohon tertentu: DFS membutuhkan memori lebih sedikit daripada BFS untuk pohon yang sangat bertingkat karena hanya perlu menyimpan node pada jalur saat ini.
- Dapat menemukan solusi dengan cepat: Jika solusi yang diinginkan berada jauh di dalam pohon, DFS dapat menemukannya lebih cepat daripada BFS.
Kekurangan:
- Tidak menjamin menemukan jalur terpendek: DFS mungkin menemukan jalur, tetapi mungkin bukan jalur terpendek.
- Potensi loop tak terbatas: Jika pohon tidak terstruktur dengan hati-hati (misalnya, mengandung siklus), DFS dapat terjebak dalam loop tak terbatas.
- Stack Overflow: Implementasi rekursif dapat menyebabkan kesalahan stack overflow untuk pohon yang sangat dalam.
Breadth-First Search (BFS)
Breadth-First Search (BFS) adalah algoritma penelusuran pohon yang menjelajahi semua node tetangga pada level saat ini sebelum beralih ke node pada level berikutnya. Algoritma ini menjelajahi pohon level demi level, dimulai dari akar. BFS biasanya diimplementasikan secara iteratif menggunakan antrean (queue).
Algoritma BFS
- Antrekan (Enqueue) node akar.
- Selama antrean tidak kosong:
- Keluarkan (Dequeue) sebuah node dari antrean.
- Kunjungi node tersebut (misalnya, cetak nilainya).
- Antrekan semua anak dari node tersebut.
Contoh Implementasi (Python)
from collections import deque
def bfs_traversal(root):
if root is None:
return
queue = deque([root])
while queue:
node = queue.popleft()
print(node.data, end=" ")
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
#Contoh Penggunaan (pohon yang sama seperti sebelumnya)
print("Penelusuran BFS:")
bfs_traversal(root) # Output: 1 2 3 4 5
Kasus Penggunaan BFS
- Menemukan jalur terpendek: BFS dijamin akan menemukan jalur terpendek antara dua node dalam graf tak berbobot. Bayangkan situs jejaring sosial. BFS dapat menemukan koneksi terpendek antara dua pengguna.
- Penelusuran graf: BFS dapat digunakan untuk menelusuri graf.
- Web crawling: Mesin pencari menggunakan BFS untuk menjelajahi web dan mengindeks halaman.
- Menemukan tetangga terdekat: Dalam pemetaan geografis, BFS dapat menemukan restoran, pom bensin, atau rumah sakit terdekat dari lokasi tertentu.
- Algoritma flood fill: Dalam pemrosesan gambar, BFS menjadi dasar untuk algoritma flood fill (misalnya, alat "ember cat").
Keunggulan dan Kekurangan BFS
Keunggulan:
- Dijamin menemukan jalur terpendek: BFS selalu menemukan jalur terpendek dalam graf tak berbobot.
- Cocok untuk menemukan node terdekat: BFS efisien untuk menemukan node yang dekat dengan node awal.
- Menghindari loop tak terbatas: Karena BFS menjelajahi level demi level, ia menghindari terjebak dalam loop tak terbatas, bahkan dalam graf dengan siklus.
Kekurangan:
- Membutuhkan banyak memori: BFS dapat membutuhkan banyak memori, terutama untuk pohon yang lebar, karena perlu menyimpan semua node pada level saat ini dalam antrean.
- Bisa lebih lambat dari DFS: Jika solusi yang diinginkan berada jauh di dalam pohon, BFS bisa lebih lambat dari DFS karena ia menjelajahi semua node di setiap level sebelum masuk lebih dalam.
Membandingkan DFS dan BFS
Berikut adalah tabel yang merangkum perbedaan utama antara DFS dan BFS:
| Fitur | Depth-First Search (DFS) | Breadth-First Search (BFS) |
|---|---|---|
| Urutan Penelusuran | Menjelajahi sejauh mungkin di sepanjang setiap cabang sebelum melakukan penelusuran balik | Menjelajahi semua node tetangga pada level saat ini sebelum beralih ke level berikutnya |
| Implementasi | Rekursif atau Iteratif (dengan stack) | Iteratif (dengan antrean) |
| Penggunaan Memori | Umumnya lebih sedikit memori (untuk pohon dalam) | Umumnya lebih banyak memori (untuk pohon lebar) |
| Jalur Terpendek | Tidak dijamin menemukan jalur terpendek | Dijamin menemukan jalur terpendek (dalam graf tak berbobot) |
| Kasus Penggunaan | Pencarian jalur, pengurutan topologis, deteksi siklus, pemecahan labirin, parsing ekspresi | Pencarian jalur terpendek, penelusuran graf, web crawling, menemukan tetangga terdekat, flood fill |
| Risiko Loop Tak Terbatas | Risiko lebih tinggi (membutuhkan struktur yang hati-hati) | Risiko lebih rendah (menjelajahi level demi level) |
Memilih Antara DFS dan BFS
Pilihan antara DFS dan BFS tergantung pada masalah spesifik yang ingin Anda pecahkan dan karakteristik pohon atau graf yang sedang Anda kerjakan. Berikut adalah beberapa panduan untuk membantu Anda memilih:
- Gunakan DFS saat:
- Pohon sangat dalam dan Anda menduga solusinya berada di kedalaman.
- Penggunaan memori menjadi perhatian utama, dan pohon tidak terlalu lebar.
- Anda perlu mendeteksi siklus dalam graf.
- Gunakan BFS saat:
- Anda perlu menemukan jalur terpendek dalam graf tak berbobot.
- Anda perlu menemukan node terdekat ke node awal.
- Memori bukan kendala utama, dan pohonnya lebar.
Di Luar Pohon Biner: DFS dan BFS dalam Graf
Meskipun kita terutama membahas DFS dan BFS dalam konteks pohon, algoritma ini juga berlaku untuk graf, yang merupakan struktur data yang lebih umum di mana node dapat memiliki koneksi arbitrer. Prinsip-prinsip intinya tetap sama, tetapi graf dapat memperkenalkan siklus, yang memerlukan perhatian ekstra untuk menghindari loop tak terbatas.
Saat menerapkan DFS dan BFS pada graf, umum untuk memelihara set atau array "dikunjungi" untuk melacak node yang telah dijelajahi. Ini mencegah algoritma mengunjungi kembali node dan terjebak dalam siklus.
Kesimpulan
Depth-First Search (DFS) dan Breadth-First Search (BFS) adalah algoritma penelusuran pohon dan graf fundamental dengan karakteristik dan kasus penggunaan yang berbeda. Memahami prinsip, implementasi, dan trade-off performanya sangat penting bagi setiap ilmuwan komputer atau insinyur perangkat lunak. Dengan mempertimbangkan masalah spesifik yang dihadapi dengan cermat, Anda dapat memilih algoritma yang sesuai untuk menyelesaikannya secara efisien. Sementara DFS unggul dalam efisiensi memori dan menjelajahi cabang-cabang dalam, BFS menjamin penemuan jalur terpendek dan menghindari loop tak terbatas, sehingga sangat penting untuk memahami perbedaan di antara keduanya. Menguasai algoritma ini akan meningkatkan keterampilan pemecahan masalah Anda dan memungkinkan Anda untuk mengatasi tantangan struktur data yang kompleks dengan percaya diri.